﻿using Meebey.SmartIrc4net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DesertKharonIrcPlugin
{
    public class IrcManager : IDisposable
    {
        private object _lock = new object();

        public delegate void ConnectionEstablishedEventHandler(object sender, EventArgs e);
        public delegate void ConnectionFailedEventHandler(object sender, EventArgs e);
        public delegate void MessageReceivedEventHandler(object sender, IrcManagerMessageReceivedEventArgs e);
        public delegate void ClearMessagesForNickEventHandler(object sender, IrcManagerMessageReceivedEventArgs e);

        public event ConnectionEstablishedEventHandler ConnectionEstablished;
        public event ConnectionFailedEventHandler ConnectionFailed;
        public event MessageReceivedEventHandler MessageReceived;
        public event ClearMessagesForNickEventHandler ClearMessagesForNick;

        private IrcManagerSettings _settings;
        private IrcClient _client;

        private List<string> _moderatorList = new List<string>();
        private List<string> _friendList = new List<string>();
        private List<string> _foeList = new List<string>();
        private List<string> _ignoreList = new List<string>();

        private Tuple<string, System.Drawing.Color> _currentUserColor;
       

        public List<string> ModeratorList
        {
            get { return _moderatorList; }
            set { _moderatorList = value == null ? new List<string>() : value; }
        }

        public List<string> FriendList
        {
            get { return _friendList; }
            set { _friendList = value == null ? new List<string>() : value; }
        }

        public List<string> FoeList
        {
            get { return _foeList; }
            set { _foeList = value == null ? new List<string>() : value; }
        }

        public List<string> IgnoreList
        {
            get { return _ignoreList; }
            set { _ignoreList = value == null ? new List<string>() : value; }
        }

        public bool IsRegistered { get { return _client != null ? _client.IsRegistered : false; } }
        public bool IsConnected { get { return _client != null ? _client.IsConnected : false; } }

        public IrcManager(IrcManagerSettings settings)
        {
            _settings = settings;
        }

        private void RaiseConnectionEstablished()
        {
            if (ConnectionEstablished != null)
                ConnectionEstablished.Invoke(this, new EventArgs());
        }

        private void RaiseConnectionFailed()
        {
            if (ConnectionFailed != null)
                ConnectionFailed.Invoke(this, new EventArgs());
        }

        public void Start()
        {
            Stop();

            _client = new IrcClient();
            _client.Encoding = Encoding.UTF8;
            _client.SendDelay = 1505;
            _client.ActiveChannelSyncing = true;
            _client.OnConnected += ClientConnected;
            _client.OnRegistered += ClientRegistered;
            _client.OnJoin += ClientJoinedChannel;
            _client.OnPart += ClientPartedChannel;
            _client.OnChannelMessage += ChannelMessageReceived;

            _client.OnConnectionError += ClientConnectionError;
            _client.OnError += ClientError;

            _client.OnQueryMessage += ClientQueryMessage;
            _client.OnRawMessage += ClientOnRawMessage;

            using (var regEvent = new ManualResetEventSlim(false))
	        {
	            _client.OnRegistered += (s, e) => regEvent.Set();

                try
                {
                    _client.Connect(_settings.HostName, _settings.Port);
                    if (_settings.LoginPreset == LoginPreset.Twitch)
                    {
                        // twitchclient 2 hides join/part messages which might help performance on large chats
                        _client.WriteLine(String.Format("TWITCHCLIENT {0}",_settings.ShowJoinsParts?"1":"2"));
                    }
                }
                catch (ConnectionException e)
                {
                    RaiseConnectionFailed();
                    return;
                }

                new Thread(new ThreadStart(IrcListenThread)).Start();
	            if (!regEvent.Wait(10000))
	            {
	                RaiseConnectionFailed();
	                Stop();
	            }
	            else
	                RaiseConnectionEstablished();
	        }
        }

        private void IrcListenThread()
        {
            _client.Listen();

            if (_client != null && _client.IsConnected)
                _client.Disconnect();
        }     

        private void ClientConnected(object sender, EventArgs e)
        {
            _client.Login(_settings.NickName, _settings.RealName, 0, _settings.UserName, _settings.Password);
        }

        private void ClientRegistered(object sender, EventArgs e)
        {
            _client.RfcJoin("#" + _settings.ChannelName);
        }

        private void ClientJoinedChannel(object sender, JoinEventArgs e)
        {
            if (_settings.ShowJoinsParts)
                RaiseMessageReceived(e.Data.Channel, e.Data.Nick, " has joined the channel.");
        }

        private void ClientPartedChannel(object sender, PartEventArgs e)
        {
            if (_settings.ShowJoinsParts)
                RaiseMessageReceived(e.Data.Channel, e.Who, " has left the channel. " + e.PartMessage);
        }

        private void ChannelMessageReceived(object sender, IrcEventArgs e)
        {
            if (e.Data.Channel.IsLike("#" + _settings.ChannelName))
            {
                if (!_ignoreList.Any(i => i.IsLike(e.Data.Nick)))
                    RaiseMessageReceived(e.Data.Channel, e.Data.Nick, e.Data.Message);
            }
        }

        private void RaiseMessageReceived(string channel, string nickName, string message)
        {
            IrcManagerMessageReceivedEventArgs eventArgs = new IrcManagerMessageReceivedEventArgs
            {
                NickName = nickName,
                Message = message
            };

            if (nickName.IsLike(_settings.BroadcasterNickName))
                eventArgs.Source = IrcManagerMessageSource.Broadcaster;
            else
            {
                if (_moderatorList.Any(i => i.IsLike(nickName)))
                    eventArgs.Source = IrcManagerMessageSource.Moderator;
                else if (_friendList.Any(i => i.IsLike(nickName)))
                    eventArgs.Source = IrcManagerMessageSource.Friend;
                else if (_foeList.Any(i => i.IsLike(nickName)))
                    eventArgs.Source = IrcManagerMessageSource.Foe;
                else
                {
                    ChannelUser user = _client.GetChannelUser(channel, nickName);
                    if (user != null && user.IsOp)
                        eventArgs.Source = IrcManagerMessageSource.Moderator;
                    else
                        eventArgs.Source = IrcManagerMessageSource.User;
                }
            }

            if (_settings.LoginPreset == LoginPreset.Twitch)
                eventArgs.Color = GetUserColor(nickName);

            if (MessageReceived != null)
                MessageReceived.Invoke(this, eventArgs);
        }

        private void ClientConnectionError(object sender, EventArgs e)
        {
        }

        private void ClientError(object sender, ErrorEventArgs e)
        {
        }

        private void ClientOnRawMessage(object sender, IrcEventArgs e)
        {
        }

        private void ClientQueryMessage(object sender, IrcEventArgs e)
        {
            if (_settings.LoginPreset == LoginPreset.Twitch)
            {
                if(e.Data.From.StartsWith("jtv")) 
                {
                    if (e.Data.Message.StartsWith("USERCOLOR"))
                    {
                        string user = e.Data.Message.Split(' ')[1];
                        string hexColor = e.Data.Message.Split(' ')[2];
                        Int32 r = Int32.Parse(hexColor.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
                        Int32 g = Int32.Parse(hexColor.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);
                        Int32 b = Int32.Parse(hexColor.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);
                        System.Drawing.Color color = System.Drawing.Color.FromArgb(r, g, b);
                        _currentUserColor = Tuple.Create(user, color);
                    }
                    else if (e.Data.Message.StartsWith("CLEARCHAT"))
                    {
                        string user = e.Data.Message.Split(' ')[1];
                        IrcManagerMessageReceivedEventArgs eventArgs = new IrcManagerMessageReceivedEventArgs
                        {
                            NickName = user
                        };
                        if (ClearMessagesForNick != null)
                            ClearMessagesForNick.Invoke(this, eventArgs);
                    }
                }
            }
        }

        private System.Drawing.Color GetUserColor(string user)
        {
            Stopwatch s = new Stopwatch();
            s.Start();
            while ( _settings.LoginPreset == LoginPreset.Twitch
                    &&(_currentUserColor == null || !user.Equals(_currentUserColor.Item1)) 
                    && s.Elapsed < TimeSpan.FromMilliseconds(50)) { }
            s.Stop();
            if (null != _currentUserColor && user.Equals(_currentUserColor.Item1))
            {
                return _currentUserColor.Item2;
            }
            // give up and generate a default http://discuss.dev.twitch.tv/t/default-user-color-in-chat/385/3
            else
            {
                List<System.Drawing.Color> DefaultColors = new List<System.Drawing.Color>
                {
                    System.Drawing.Color.FromArgb(255,0,0),
                    System.Drawing.Color.FromArgb(0,0,255),
                    System.Drawing.Color.FromArgb(0,255,0),
                    System.Drawing.Color.FromArgb(178,34,34),
                    System.Drawing.Color.FromArgb(255,127,80),
                    System.Drawing.Color.FromArgb(154,205,50),
                    System.Drawing.Color.FromArgb(255,69,0),
                    System.Drawing.Color.FromArgb(46,139,87),
                    System.Drawing.Color.FromArgb(218,165,32),
                    System.Drawing.Color.FromArgb(210,105,30),
                    System.Drawing.Color.FromArgb(95,158,160),
                    System.Drawing.Color.FromArgb(30,144,255),
                    System.Drawing.Color.FromArgb(255,105,180),
                    System.Drawing.Color.FromArgb(138,43,226),
                    System.Drawing.Color.FromArgb(0,255,127),
                };
                char[] cs = user.ToCharArray();
                var n = cs[0] + cs[cs.Length - 1];
                return DefaultColors[n % DefaultColors.Count];
            }
        }

        public void Stop()
        {
            if (_client != null)
            {
                if (_client.IsConnected)
                    _client.Disconnect();
            }
        }

        public void Dispose()
        {
            if (_client != null)
            {
                if (_client.IsConnected)
                    _client.Disconnect();
            }
        }
    }
}